﻿using System;
using System.Configuration;
using System.Collections.Generic;
using System.Threading.Tasks;
using RabbitMQ.Client;
using RabbitMQ.Client.Exceptions;
using YEA.Infrastructure.Gateway.Config;
using YEA.Infrastructure.Gateway.Admin;

namespace YEA.Infrastructure.Gateway
{
    public class GatewayFactory : IDisposable, IGatewayFactory
    {
        protected Dictionary<string,IConnection> _connections;
        protected System.Configuration.Configuration config;
        protected bool disposed;
        public GatewayFactory()
        {
            disposed = false;
            config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
            Initialize();
        }
        ~GatewayFactory()
        {
            Dispose(false);
        }

        private void Initialize()
        {
            _connections = new Dictionary<string,IConnection>();
            InitializeConnections();
            RabbitAdmin.InitializeExchanges(_connections);
        }

        public void InitializeConnections()
        {
            System.Configuration.Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
            var objects = config.GetSection("AMQPConnection/ConnectionSettings") as ConnectionSection;
            if (objects == null )
                throw new ConfigurationErrorsException("The AMQPConnection/ConnectionSettings configuration section is missing from the configuration file");
            if( objects.ConnectionList.Count == 0)
                throw new ConfigurationErrorsException("The AMQPConnection/ConnectionSettings requires a Connection element to communicate with the RabbitMQ server");

            Parallel.For(0, objects.ConnectionList.Count, i =>
            {

                var con = objects.ConnectionList[i];
                RabbitMQ.Client.ConnectionFactory factory = new RabbitMQ.Client.ConnectionFactory();
                factory.Endpoint = new AmqpTcpEndpoint(con.Server);
                factory.UserName = con.UserName;
                factory.Password = con.Password;
                lock (_connections)
                {
                    _connections.Add(con.Name, factory.CreateConnection());
                }
            });
        }
        private ConnectionSection GetConfigSection()
        {
            var cs = config.GetSection("AMQPConnection/ConnectionSettings") as ConnectionSection;
            if (cs == null)
                throw new ConfigurationErrorsException("The AMQPConnection/ConnectionSettings configuration section is missing from the configuration file");
            return cs;
        }
        
        public IPublisher GetPublisher(string PublisherName, ConvertToMessage converter)
        {
            var cs = GetConfigSection();

            var cp = cs.PublisherList[PublisherName];
            if (cp == null)
                throw new ConfigurationErrorsException("The AMQPConnection/ConnectionSettings Publisher section is missing from the configuration file");

            return new Publisher(GetConnection(cp.Connection), converter, cp.Exchange){Immediate = cp.Immediate,Mandatory = cp.Mandatory};
        }
                
        public IAsyncConsumerProcessor GetAsyncReceiver( string ReceiverName, MessageConsumer handler)
        {
            var cs = GetConfigSection();
            var cr = cs.AsyncReceiverList[ReceiverName];
            if( cr == null)
                throw new ConfigurationErrorsException(String.Format("The AMQPConnection/ConnectionSettings AsyncReciever {0} is missing from the configuration file", ReceiverName));
            AsyncConsumerProcessor acp = new AsyncConsumerProcessor(GetConnection(cr.Connection), cr.MaxThreads, handler, cr.Queue);
            return acp;
        }

        private IConnection GetConnection(string name)
        {
            var _connection = _connections[name];
            if (_connection == null)
                throw new ConfigurationErrorsException(String.Format("A connection with the name {0} does not exist in the configuration file", name));
            return _connection;
        }
        public void Dispose()
        {
            Dispose(true);
        }

        protected virtual void Dispose(bool disposing)
        {
            try
            {
                if (!disposing && !disposed)
                    Parallel.ForEach(_connections, item => { item.Value.Close(1, "object destructor called"); });
                if (disposing && !disposed)
                {
                    Parallel.ForEach(_connections, item => { item.Value.Close(1, "object destructor called"); }); 
                    disposed = true;
                }
            }
            catch (AggregateException ex)
            {
                ///We called close on an alreadhy closed connection - no harm done.
            }
            config = null;
            _connections = null;
            if( disposing)
                GC.SuppressFinalize(this);

        }
    }
}
